/*!  \file  TaskManager.cpp
 *	  \brief  
 * 
 *   \details   details
 *   \author    Yordan Madzhunkov German Research School for Simulation Sciences GmbH
 *   \version   1.0
 *   \date      07/22/2013 01:32:15 PM
 *   \copyright GNU Public License.
 */

#include <pthread.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <float.h>
#include "macros.h"
#include "Lock.h"
#include "Queue.h"
#include "ThreadPool.h"
#include "Task.h"
#include "MemoryPool.h"
#include "TaskManager.h"
#include "GKManager.h"
#include "BiMRRR.h"
#include "GK.h"

TaskManager::TaskManager() {
	ready       = NULL;
	taskStorage = NULL;
	taskIDPool  = NULL;
	locks       = NULL;
	gkManager   = NULL;

	maxNTasks       = 1024;
	maxNTaskInQueue = 512;

	nThreads = ThreadPool::getOnlineCores(); 

	maxNLocks       = 4 * nThreads ;
	needAllocation  = true;
	notFinished     = false;
}
void TaskManager::setN(const int n_) {
	n = n_;

	delocate();

	maxNLocks       = max(n + 4, 4 * nThreads) ;
	maxNTasks       = min(4 * n, 1024);
	maxNTaskInQueue = min(2 * n, 512);
	maxNGK          = n;
	
	
}
TaskManager::~TaskManager() {
	delocate();
}
void TaskManager::allocate() {
	if ( needAllocation) {
		delocate();
		//ready       = new NoLimitQueue<int>(maxNTaskInQueue, nThreads);
		ready       = new NoLimitQueue<int>(maxNTaskInQueue);

		taskStorage = new Task      [maxNTasks];
		taskIDPool  = new MemoryPool(maxNTasks, nThreads); 

		locks       = new Lock      [maxNLocks];
		gkManager   = new GKManager (maxNGK, n);
		needAllocation = false;
	}
}
void TaskManager::delocate() {
	delObj(  gkManager    );
	delArr(  locks        );
	delObj(  taskIDPool   );
	delArr(  taskStorage  );
	delObj(  ready        );
	needAllocation = true;
}
void * TaskManagerWorkerFunc(void * arg ) {
	WorkerInfo * wInfo = reinterpret_cast<WorkerInfo*>(arg);
	assert(wInfo != NULL);
	return wInfo->manager->workerFunction(wInfo->tID);
}


bool TaskManager::perform     ( Task & t, const int nThreads_) {

	nThreads = nThreads_;
	if (t.type == Task::BiMRRRTask) {
		BiMRRR * bimrrr = reinterpret_cast<BiMRRR*>(t.dataPtr);
		n = bimrrr->getN();	
	} else if (t.type == Task::IdentifyClusters) {
		GKBisector * bis = reinterpret_cast<GKBisector*>(t.dataPtr);
		n = bis->getN();	
	} else {
		return false;
	}

	setN(n); // take into account if we changed the nThreads

	allocate();
	
	WorkerInfo args[nThreads];
	void *      res[nThreads]; 

	memset(res, 0, sizeof(res[0]) * nThreads);
	ThreadPool threadPool(nThreads);
	for (int i = 0; i < nThreads; i++) {
       args[i].manager = this;
       args[i].tID     = i;
	}
	reset();
	TaskID taskID;
	if (!taskIDPool->allocate(taskID, 0) )	return false;

	getTask(taskID).reset(t.type, t.dataPtr, t.localID, -1);
	getTask(taskID).threadID = 0;
	getTask(taskID).precompute(*this); // this should add subtasks to work queue
	notFinished = true;
	if (ready->size() > 0) // if there is something to compute
		threadPool.execute(&TaskManagerWorkerFunc, nThreads, args, res);


	bool success = true;
	for (int i = 0; i < nThreads_; i++) 
		success &= res[i] == NULL;

	taskIDPool->delocate(taskID, 0);

	return success;
}
void TaskManager::reset() {
	if (ready) ready->reset();
}
void * TaskManager::workerFunction(const int tID)  {
	TaskID taskID;
	while (notFinished) {
//		if (ready->tryPop(taskID, tID) ) {
		if (ready->tryPop(taskID) ) {
//			char buf[128];
//			sprintf(buf, "poped %d by %d\n", taskID, tID);
//			cout << buf;
			process(taskID, tID);
		} else {
			cout << "NEEEE " << endl;
			struct timespec req, rem;
			req.tv_sec  = 0;        /*  seconds */
    		req.tv_nsec = 2000;       /*  nanoseconds */
			nanosleep(&req, &rem);
		}
	}
	return NULL;
}
void TaskManager::informParent(const TaskID parentID, const TaskID childID, const int tID) {
//	char buf[128];
//	sprintf(buf, "TaskID=%d finished\n", childID);
//	cout << buf;
	if ( parentID >= 0 ) { // inform the parent that subtask is done
		Task & parent = getTask(parentID);
		if (parent.childFinished(*this, childID) ) { // parent task are complete
			// inform the parent's parent
			TaskID grandfatherID = parent.getParentID();
			informParent(grandfatherID, parentID, tID);
			parent.finish(*this); // clean up, if nessesery
			taskIDPool->delocate(parentID, tID); // free this taskID
		}
	} else {
		finish(); // job's done
	}
}
void TaskManager::process(TaskID taskID, const int tID) {
	Task & t = getTask(taskID);	
	t.threadID = tID;
//	t.print(cout, taskID);
	if (t.precompute(*this)) { // no subtasks were created
		TaskID parentID = t.getParentID();
	cout <<  __PRETTY_FUNCTION__  <<" line : " << __LINE__ << endl;
		informParent(parentID, taskID, tID); // inform that subtask is done
	cout <<  __PRETTY_FUNCTION__  <<" line : " << __LINE__ << endl;
		t.finish(*this);						    // clean up, if nessesery
		taskIDPool->delocate(taskID, tID);   // free this taskID
	cout <<  __PRETTY_FUNCTION__  <<" line : " << __LINE__ << endl;
	} 
}
void TaskManager::addToReady  (const TaskID taskID) {
	char buf[128];
//	sprintf(buf, "added task %d\n", taskID);
//	cout << buf;
	ready->push(taskID);
}
